﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LibXML;
using StateMachine;
using System.Xml.Linq;
using System.Windows;

namespace LilTmSimFacade
{
	public enum StateMachines { Turing, PDA, FA }

	public class StateMachineProvider
	{
		public string FilePath { get { return Manager.FilePath; } }
		public TuringMachine Machine { get; set; }
		private StateMachines MachineType { get; set; }
		private IEnumerable<StateMachine.State> States { get; set; }
		private IEnumerable<StateMachine.Transition> Transitions { get; set; }
		private IEnumerable<StateMachine.Note> Notes { get; set; }
		private XMLManager Manager { get; set; }

		public StateMachineProvider(StateMachines machineType)
		{
			// Create the machine type
			Manager = new XMLManager();
			MachineType = machineType;

			// Create the states/transitions/notes
			States = new List<StateMachine.State>();
			Transitions = new List<StateMachine.Transition>();
			Notes = new List<StateMachine.Note>();

			// Create the machine
			CreateMachine();
		}

		public StateMachineProvider(string path)
		{
			// Load the machine type
			Manager = new XMLManager(path);
			MachineType = (StateMachines)Enum.Parse(typeof(StateMachines), Manager.MachineType, true);

			// Load the states
			States = Manager.States.Select(state =>
				new StateMachine.State()
				{
					Id = state.Id,
					Label = state.Label,
					Location = new Point(state.X, state.Y),
					Name = state.Name,
					IsFinal = state.Final,
					IsInitial = state.Initial,
					ShowLabel = state.ShowLabel
				}
			).ToList();

			// Load the transitions
			Transitions = Manager.Transitions.Select(transition =>
				new StateMachine.Transition()
				{
					Direction = TryParseTapeMovement(transition.Move),
					Read = String.IsNullOrEmpty(transition.Read) ? (char?)null : transition.Read[0],
					Write = String.IsNullOrEmpty(transition.Write) ? (char?)null : transition.Write[0],
					Push = String.IsNullOrEmpty(transition.Push) ? (char?)null : transition.Push[0],
					Pop = String.IsNullOrEmpty(transition.Pop) ? (char?)null : transition.Pop[0],
					StartState = States.FirstOrDefault(state => state.Id == transition.From),
					EndState = States.FirstOrDefault(state => state.Id == transition.To),
					IsPathVisible = transition.IsPathVisible,
					MidPoint = new Point(transition.X, transition.Y)
				}
			).ToList();

			// Attach the transitions
			foreach (StateMachine.State state in States)
			{
				state.Transitions.AddRange(Transitions.Where(t => t.StartState.Id == state.Id));
			}

			// Load the notes
			Notes = Manager.Notes.Select(note =>
				new StateMachine.Note()
				{
					Location = new Point(note.X, note.Y),
					Text = note.Text
				}
			).ToList();

			// Create the machine
			CreateMachine();
		}

		/// <summary>
		/// Try to parse the given tape movement string into an enumeration meaning the same thing
		/// </summary>
		/// <param name="move">The string of the movement to parse</param>
		/// <returns>The equivalent enumeration for the given input string</returns>
		private TapeMovement TryParseTapeMovement(string move)
		{
			return Enum.GetNames(typeof(TapeMovement)).Contains(move) ? (TapeMovement)Enum.Parse(typeof(TapeMovement), move) : TapeMovement.R;
		}

		/// <summary>
		/// Create the machine from the file
		/// </summary>
		private void CreateMachine()
		{
			switch (MachineType)
			{
//				case StateMachines.Turing:
				default:
					Machine = new TuringMachine()
					{
						States = new StateCollection(States),
						Notes = new NoteCollection(Notes),
						StartState = States.FirstOrDefault(state => state.IsInitial),
						EndState = States.FirstOrDefault(state => state.IsFinal)
					};
					break;
			}
		}

		/// <summary>
		/// Save a machine to the file from which it was loaded.
		/// </summary>
		public void Save()
		{
			Save(FilePath);
		}

		/// <summary>
		/// Save a machine to the file that is specified.
		/// </summary>
		/// <param name="path">The file to save the machine to.</param>
		public void Save(string path)
		{
			// Set the machine type
			Manager.MachineType = MachineType.ToString().ToLower();

			// Set the states
			Manager.States = Machine.States.Select(state => new LibXML.State()
			{
				Final = state.IsFinal,
				Id = state.Id,
				Initial = state.IsInitial,
				Label = state.Label,
				Name = state.Name,
				X = state.Location.X,
				Y = state.Location.Y,
				ShowLabel = state.ShowLabel
			});

			// Set the transitions
			Manager.Transitions = Machine.States.SelectMany(state => state.Transitions).Select(transition => new LibXML.Transition()
			{
				From = transition.StartState.Id,
				Move = transition.Direction.ToString(),
				Pop = transition.Pop.HasValue ? transition.Pop.Value.ToString() : string.Empty,
				Push = transition.Push.HasValue ? transition.Push.Value.ToString() : string.Empty,
				Read = transition.Read.HasValue ? transition.Read.Value.ToString() : string.Empty,
				To = transition.EndState.Id,
				Write = transition.Write.HasValue ? transition.Write.Value.ToString() : string.Empty,
				X = transition.MidPoint.X,
				Y = transition.MidPoint.Y,
				IsPathVisible = transition.IsPathVisible
			});

			// Set the notes
			Manager.Notes = Machine.Notes.Select(note => new LibXML.Note()
			{
				Text = note.Text,
				X = note.Location.X,
				Y = note.Location.Y
			});

			// Save the manager
			Manager.SaveManager(path);
		}
	}
}
